查看原文
其他

可视化神器Plotly玩转子图

Peter 尤而小屋 2022-05-28

可视化神器Plotly玩转子图绘制

大家好,我是Peter~

很长时间没有Plotly绘图的文章,之前已经介绍如何绘制柱状图、饼图、小提琴图、桑基图等,它们都是单一种类图形的绘制。

今天给大家带来的是一篇关于Plotly如何绘制多子图的文章,在一个画布中如何实现多种类型图形的绘制。

Plotly连载文章

多子图绘制

Plotly有两种方式来绘制子图,基于plotly_express和 graph_objects。

但是plotly_express只支持 facet_plots(切面图) 和 marginal distribution subplots(边际分布子图),只有graph_objects是基于make_subplots模块才能够绘制真正意义上的多子图。下面通过实际例子来讲解。

import pandas as pd
import numpy as np

import plotly_express as px
import plotly.graph_objects as go

# 绘制子图
from plotly.subplots import make_subplots

最重要的是要导入make_subplots模块。

基于plotly_express

plotly_express绘制“子图”是通过参数marginal_xmarginal_y 来实现的,表示在边际上图形的类型,可以是"histogram", "rug", "box", or "violin"。

基于切面图facet_plots

切面图是由具有相同轴集的多个子图组成的图形,其中每个子图显示数据的子集,也称之为:trellis(网格) plots or small multiples。直接上官方英文,感觉更合适,暂时找不到比较恰当的中文来翻译。

不同图形切面展示

先导入内置的消费数据集:

1、基于散点图的切面图形

fig = px.scatter(tips,  #  数据
                 x="total_bill"# xy轴 
                 y="tip"
                 color="smoker"# 颜色
                 facet_col="day"  # 列方向切面字段
                )
fig.show()

2、基于柱状图的切面展示

# 2、柱状图切面

fig = px.bar(tips, 
             x="size"
             y="total_bill"
             color="day"
             facet_row="smoker"  # 行方向切面字段:是否抽烟
            )
fig.show()

控制子图个数

3、wrapping column Facets:控制显示元素个数的切面图

当我们指定的某个切面的字段的取值有很多种不同的情况,我们可以通过facet_col_wrap参数来控制每行最多显示的图形个数,wrap可以理解成:被限制的意思,就是每行限制显示多少。

使用内置的GDP数据集:

# 3、被限制每行图形个数的切面图

fig = px.scatter(gdp,   # 数据集
                 x='gdpPercap',  # x、y、颜色、点的大小size
                 y='lifeExp'
                 color='continent'
                 size='pop',
                 facet_col='continent'# 列切面字段
                 facet_col_wrap=3 # 每行最多3个图形
                )
fig.show()

上面的切面图形的col字段是洲,最多只有5个洲。下面的图形选用时间year:

fig = px.scatter(gdp,   # 数据集
                 x='gdpPercap',  # x、y、颜色、点的大小size
                 y='lifeExp'
                 color='continent'
                 size='pop',
                 facet_col='year'# 列切面字段
                 facet_col_wrap=3  # 每行最多3个图形
                )
fig.show()

每行最多显示4个图形:

fig = px.scatter(gdp, 
                 x='gdpPercap'
                 y='lifeExp'
                 color='continent'
                 size='pop',
                 facet_col='year'
                 facet_col_wrap=4  # 每行最多4个图形
                )
fig.show()

子图坐标轴设置

默认情况下子图的y轴是相同的:

# 独立轴的切面:默认情况是相同的y轴
# 默认下y轴的取值范围相同
fig = px.scatter(tips, 
                 x="total_bill"
                 y="tip"
                 color='day'
                 facet_row="time"
                )
fig.show()

通过参数设置不共享y轴:

fig = px.scatter(tips, 
                 x="total_bill"
                 y="tip"
                 color='day'
                 facet_row="time"  # 列方向上切面图
                )

# 设置不共享y轴,对应的是facet_row
fig.update_yaxes(matches=None)

fig.show()

如果是facet_col在列方向上的切面,则可以设置不共享x轴

fig = px.scatter(tips, 
                 x="total_bill"
                 y="tip"
                 color='day'
                 facet_col="time"  # 列方向上切面图
                )

# 设置不共享x轴,对应的是facet_col
fig.update_xaxes(matches=None)

fig.show()

子图标题设置

fig = px.scatter(tips,
                 x="total_bill"
                 y="tip"
                 color="time",
                 facet_col="smoker"
                )
fig.show()

通过设置改变子图的标题:

fig = px.scatter(tips, 
                 x="total_bill"
                 y="tip"
                 color="time",
                 facet_col="smoker"
                )

# 增加代码:对每行标题通过=切割,取出最后的元素
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))  

fig.show()

基于边际图Marginal

该方法主要是通过marginal_x和marginal_y来实现的。首先导入数据集:

基于不同图形边际图

1、基于散点图:

fig = px.scatter(iris,  # 数据集 
                 x="sepal_length",  # 指定xy轴
                 y="sepal_width",
                 marginal_x="rug",  # 边际图形类型:直方图
                 marginal_y="histogram"  # 轴须图
                )

fig.show()

2、基于密度热图的边际图形设置:

fig = px.density_heatmap(
    iris,  # 数据
    x="sepal_width",  # 两个轴
    y="sepal_length",  
    marginal_x="violin",  # 边际图:小提琴和箱型图
    marginal_y="box")

fig.show()

3、边际图颜色设置

fig = px.scatter(iris,
                 x="sepal_length"
                 y="sepal_width"
                 color="species",   # 颜色的设置同样适用于边际图
                 marginal_x="violin"
                 marginal_y="box",
                 title="边际图颜色设置")
fig.show()

边际图和切面图连用

fig = px.scatter(
    tips, 
    x="total_bill",
    y="tip"
    color="sex"
    facet_col="day",  # 日期字段切面
    marginal_x="violin"  # 边际图用小提琴图
)

fig.show()

基于graph_objects

graph_objects方式其实是通过make_subplots函数来实现的。一定要先导入:

# 这种方式一定要导入的模块

from plotly.subplots import make_subplots
import plotly.graph_objects as go

基础子图

# 两个基本参数:设置行、列
fig = make_subplots(rows=1, cols=2)  # 1行2列

# 添加两个数据轨迹,构成两个图形
fig.add_trace(
    go.Scatter(x=[123], y=[51015]),
    row=1, col=1  # 第一行第一列
)

fig.add_trace(
    go.Scatter(x=[203040], y=[607080]),
    row=1, col=2  # 第一行第二列
)

# 设置图形的宽高和标题
fig.update_layout(height=600
                  width=800
                  title_text="子图制作")
fig.show()
fig = make_subplots(rows=3, cols=1)  # 3行1列

# 添加3个数据轨迹
fig.add_trace(
    go.Scatter(x=[123], y=[51015]),
    row=1, col=1  # 1*1
)

fig.add_trace(
    go.Scatter(x=[203040], y=[607080]),
    row=2, col=1  # 2*1
)

fig.add_trace(
    go.Scatter(x=[506070], y=[110120130]),
    row=3, col=1  # 3*1
)

fig.update_layout(height=600
                  width=800
                  title_text="子图制作")
fig.show()

多行多列子图

多行多列的时候,我们可以指定第一个图形的位置,表示图形从哪里开始。默认左上角是第一个图形的位置。

\fig = make_subplots(rows=2, cols=2,# 2行2列
                    start_cell="bottom-left"# 第一个图形的位置,两个选择:bottom-left', 'top-left
                   )  

# 添加4个数据轨迹
fig.add_trace(
    go.Bar(x=[123], y=[51015]),
    row=1, col=1  # 1*1
)

fig.add_trace(
    go.Scatter(x=[203040], y=[607080]),
    row=1, col=2  # 1*2
)

fig.add_trace(
    go.Scatter(x=[506070], y=[110120130]),
    row=2, col=1  # 2*1
)
fig.add_trace(
    go.Bar(x=[506070], y=[110120130]),
    row=2, col=2  # 2*2
)

fig.update_layout(height=600
                  width=800
                  title_text="子图制作")
fig.show()

多子图标题设置

很多时候我们要给每个子图取名,用subplot_titles来实现

fig = make_subplots(rows=2, cols=2,
                    start_cell="bottom-left"# 'bottom-left', 'top-left
                    subplot_titles=["子图1","子图2","子图3","子图4"]  # 每个子图的名字
                   )  

# 添加4个数据轨迹
fig.add_trace(
    go.Bar(x=[123], y=[51015]),
    row=1, col=1  # 1*1
)

fig.add_trace(
    go.Scatter(x=[203040], y=[607080]),
    row=1, col=2  # 1*2
)

fig.add_trace(
    go.Scatter(x=[506070], y=[110120130]),
    row=2, col=1  # 2*1
)
fig.add_trace(
    go.Bar(x=[506070], y=[110120130]),
    row=2, col=2  # 2*2
)

fig.update_layout(height=600
                  width=800
                  title_text="多行多列子图制作")
fig.show()

多子图标注Annotations

在子图中我们还可以给数据添加标注:

fig = make_subplots(rows=1, cols=2,
                    subplot_titles=["子图1","子图2"]  # 子图名字
                   )  

# 添加数据
fig.add_trace(
    go.Bar(x=[123], 
           y=[51015],
           text=["文字1""文字2""文字3"], # 标注内容
           textposition="inside"    # 位置
          ),
    row=1, col=1  # 1*1
)

# 添加数据
fig.add_trace(
    go.Scatter(x=[123], 
           y=[51015],
           mode="markers+text",  # 散点图的数据显示形式
           text=["文字4""文字5""文字6"],  # 标注内容
           textposition="bottom center"    # 位置
          ),
    row=1, col=2  # 1*2
)


fig.update_layout(height=600
                  width=800
                  title_text="多子图添加标注")
fig.show()

子图宽度设置

上面绘制的多子图都是大小相同的,我们可以通过参数来进行设置显示不同的大小:column_widths

fig = make_subplots(rows=1
                    cols=2,
                    column_widths=[0.35,0.65],  # 重点:两个子图的宽度占比
                    subplot_titles=["子图1","子图2"]  # 名字
                   )  

fig.add_trace(
    go.Bar(x=[123], 
           y=[51015],
           text=["文字1""文字2""文字3"], # 标注内容
           textposition="inside"    # 位置
          ),
    row=1, col=1  # 1*1
)

# 添加数据
fig.add_trace(
    go.Scatter(x=[123], 
           y=[51015],
           mode="markers+text",  # 散点图的数据显示形式
           text=["文字4""文字5""文字6"],  # 标注内容
           textposition="bottom center"    # 位置
          ),
    row=1, col=2  # 1*2
)

fig.update_layout(height=600
                  width=800
                  title_text="多子图添加标注")
fig.show()

共享x轴

共享x轴指的是针对在同列行中的多个图形共享x轴:

fig = make_subplots(rows=3
                    cols=1,
                    # 重点参数
                    shared_xaxes=True,  # 设置共享x轴
                    vertical_spacing=0.03,  # 图之间的间隙大小
                    subplot_titles=["子图1","子图2","子图3"]  # 名字
                   )  

fig.add_trace(
    go.Bar(x=[123], 
           y=[51015],
           text=["文字1""文字2""文字3"], 
           textposition="inside"    # 位置
          ),
    row=1, col=1  # 1*1
)

# 添加数据
fig.add_trace(
    go.Scatter(x=[456], 
           y=[51015],
           mode="markers+text",  
           text=["文字4""文字5""文字6"],  
           textposition="bottom center"    
          ),
    row=2, col=1  # 2*1
)


# 添加数据
fig.add_trace(
    go.Scatter(x=[102030], 
           y=[253045],
           mode="markers+text",  # 散点图的数据显示形式
           text=["文字7""文字8""文字9"],  # 标注内容
           textposition="bottom center"    # 位置
          ),
    row=3, col=1  # 3*1
)


fig.update_layout(height=600
                  width=1000
                  title_text="多子图添加标注")
fig.show()

共享y轴

共享y轴指的是针对在同一行中的多个图形共享y轴:

fig = make_subplots(rows=2, cols=2,   # 2*2
                    subplot_titles=["子图1","子图2","子图3","子图4"],
                    shared_yaxes=True  # 重点:共享y轴
                   )

fig.add_trace(go.Scatter(x=[123], y=[234]),  # 两个轴的数据
              row=1, col=1)  # 行列位置

fig.add_trace(go.Scatter(x=[203040], y=[567]),
              row=1, col=2)

fig.add_trace(go.Bar(x=[203040], y=[204010]),
              row=2, col=1)

fig.add_trace(go.Scatter(x=[405060], y=[708090]),
              row=2, col=2)

fig.update_layout(height=600, width=600,
                  title_text="多子图共享y轴")
fig.show()

坐标轴自定义

from plotly.subplots import make_subplots
import plotly.graph_objects as go


fig = make_subplots(
    rows=2
    cols=2
    subplot_titles=("Plot 1""Plot 2""Plot 3""Plot 4")
)

# 添加不同数据
fig.add_trace(go.Scatter(x=[123], 
                         y=[456]), 
              row=1, col=1)

fig.add_trace(go.Scatter(x=[203040], 
                         y=[506070]), 
              row=1, col=2)
fig.add_trace(go.Scatter(x=[300400500], 
                         y=[600700800]), 
              row=2, col=1)

fig.add_trace(go.Scatter(x=[400050006000], 
                         y=[700080009000]), 
              row=2, col=2)

# 自定义x轴
fig.update_xaxes(title_text="xaxis—1 标题", row=1, col=1)  # 正常显示
fig.update_xaxes(title_text="xaxis-2 标题", range=[1050], row=1, col=2)  # 设置范围range
fig.update_xaxes(title_text="xaxis-3 标题", showgrid=False, row=2, col=1)  # 不显示网格线
fig.update_xaxes(title_text="xaxis-4 标题", type="log", row=2, col=2)  # 基于对数

# 自定义y轴
fig.update_yaxes(title_text="yaxis 1 标题", row=1, col=1)
fig.update_yaxes(title_text="yaxis 2 标题", range=[4080], row=1, col=2)
fig.update_yaxes(title_text="yaxis 3 标题", showgrid=False, row=2, col=1)
fig.update_yaxes(title_text="yaxis 4 标题", row=2, col=2)

# Update title and height
fig.update_layout(title_text="自定义子图轴坐标", height=700)

fig.show()

共享颜色轴

使用的参数是coloraxis

fig = make_subplots(rows=1, cols=2
                    shared_yaxes=True)   # 在y轴方向上共享

fig.add_trace(go.Bar(x=[123], y=[456],
                    marker=dict(color=[456],
                                coloraxis="coloraxis")),
              11)  # 直接表示位置在(1,1)

fig.add_trace(go.Bar(x=[123], y=[235],
                    marker=dict(color=[235],
                                coloraxis="coloraxis")),
              12# 位置在(1,2)

fig.update_layout(coloraxis=dict(colorscale='Bluered'),   # 颜色轴
                  showlegend=False)  # 不显示图例

fig.show()

子图位置自定义

子图位置的自定义是通过参数spec来实现的,spec是一个2维的列表集合,列表中包含rows和cols两个参数。

比如我们想绘制2*2的图形,但是只有3个图形,那么肯定有个图形会占据2行1列或者是1行2列的位置,通过例子来解释。

fig = make_subplots(
    rows=2, cols=2,
    specs=[[{}, {}],  # 1*1,1*2
           [{"colspan"2}, None]],  # 2*1的位置占据两列,2*2的位置没有图
    subplot_titles=("子图1","子图2""子图3"))

fig.add_trace(go.Scatter(x=[12], y=[56]),
                 row=1, col=1# 1*1

fig.add_trace(go.Scatter(x=[46], y=[89]),
                 row=1, col=2)  # 1*2

fig.add_trace(go.Scatter(x=[123], y=[252]),
                 row=2, col=1)  # 2*1占据两行

fig.update_layout(showlegend=False,   # 不显示图例
                  title_text="子图位置自定义")
fig.show()
fig = make_subplots(
    rows=2, cols=2,
    specs=[[{"rowspan":2}, {}],  # 1*1 占据两列,1*2
           [None,{}]],  # 2*1的位置没有图
    subplot_titles=("子图1","子图2""子图3"))

fig.add_trace(go.Scatter(x=[12], y=[56]),
                 row=1, col=1# 1*1  占据两列

fig.add_trace(go.Scatter(x=[46], y=[89]),
                 row=1, col=2)  # 1*2

fig.add_trace(go.Scatter(x=[123], y=[252]),
                 row=2, col=2)  # 2*1

fig.update_layout(showlegend=False,   # 不显示图例
                  title_text="子图位置自定义")
fig.show()

让我们看一个更为复杂的例子:

fig = make_subplots(
    rows=5, cols=2,  # 5*2的图形
    specs=[[{}, {"rowspan"2}], # 1*1 ;2*1的位置占据两行rows
           [{}, None],  # 2*1;2*2的位置已经被上面的2*1占据
           [{"rowspan"2"colspan"2}, None],  # 3*1的位置占据2行2列所以,第3、4行的两列只有单个图形
           [NoneNone],
           [{}, {}]]  # 5*1;5*2
)

fig.add_trace(go.Scatter(x=[12], y=[12], name="(1,1)"), row=1, col=1)

fig.add_trace(go.Scatter(x=[12], y=[12], name="(1,2)"), row=1, col=2)

fig.add_trace(go.Scatter(x=[12], y=[12], name="(2,1)"), row=2, col=1)

fig.add_trace(go.Scatter(x=[12], y=[12], name="(3,1)"), row=3, col=1)

fig.add_trace(go.Scatter(x=[12], y=[12], name="(5,1)"), 5,1# row和col可以省略
fig.add_trace(go.Scatter(x=[12], y=[12], name="(5,2)"), 52)

fig.update_layout(height=600
                  width=600
                  title_text="多子图位置自定义")

fig.show()

子图类型自定义

子图可供选择的图形类型:

  • "xy": 二维的散点scatter、柱状图bar等

  • "scene": 3维的scatter3d、球体cone

  • "polar": 极坐标图形如scatterpolar, barpolar等

  • "ternary": 三元图如scatterternary

  • "mapbox": 地图如scattermapbox

  • "domain": .针对有一定域的图形,如饼图pie, parcoords, parcats,

from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
    rows=2, cols=2,
    specs=[[{"type""xy"}, {"type""polar"}],
           [{"type""domain"}, {"type""scene"}]],  # 通过type来指定类型
)

fig.add_trace(go.Bar(y=[231]),
              11)

fig.add_trace(go.Barpolar(theta=[04590], r=[231]),
              12)

fig.add_trace(go.Pie(values=[231]),
              21)

fig.add_trace(go.Scatter3d(x=[231], y=[000],
                           z=[0.512], mode="lines"),
              22)

fig.update_layout(height=700, showlegend=False)

fig.show()

多子图类型和位置的连用:

from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
    rows=2, cols=2,
    specs=[[{"type""xy"}, {"type""polar"}],  # 设置类型
           [{"colspan":2,"type""domain"},None]], # 设置位置:2*1的位置占据两列; 2*2没有图形
)

fig.add_trace(go.Bar(y=[231]),
              11)

fig.add_trace(go.Barpolar(theta=[04590], r=[231]),
              12)

fig.add_trace(go.Pie(values=[231]),
              21)  

fig.update_layout(height=700, showlegend=False)

fig.show()

参数解释

最后附上官网地址,多多学习:https://plotly.com/python/subplots/

plotly.subplots.make_subplots(rows=1,   # 行列数值决定位置
                              cols=1
                              shared_xaxes=False,  # 是否共享xy轴
                              shared_yaxes=False
                              start_cell='top-left',   # 第一个图形的位置
                              print_grid=False,  # 是否输出表格参数
                              horizontal_spacing=None,   # 垂直和水平方向上的间隔
                              vertical_spacing=None
                              subplot_titles=None,   # 子图标题
                              column_widths=None,  # 列宽和行高
                              row_heights=None
                              specs=None,  # 子图类型
                              insets=None
                              column_titles=None# 行和列的标题
                              row_titles=None
                              x_title=None,  # xy轴axis的标题
                              y_title=None
                              figure=None
                              **kwargs)

通过下面的方式查看帮助文档:

from plotly.subplots import make_subplots
help(make_subplots)


推荐阅读


数据分析师=7大主题,24份资料
55个案例:吃透Python字符串格式化
桑基图或许可以告诉你打工人的故事!
生日快乐:尤而小屋两周岁啦
Python入门-字符串初相识
图解Pandas的缺失值处理

尤而小屋,一个温馨的小屋。小屋主人,一手代码谋求生存,一手掌勺享受生活,欢迎你的光临

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存